/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.
 * Description: Hmfs-tools zoned
 */

#include "hmfs_zoned.h"

#include <cerrno>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <fcntl.h>
#include <filesystem>
#include <fstream>
#include <libgen.h>
#include <linux/limits.h>
#include <sstream>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/sysmacros.h>
#include <unistd.h>

#include "hmfs_log.h"
#include "securec.h"

namespace OHOS {
namespace Hmfs {
HmfsZoned &HmfsZoned::GetInstance()
{
    static HmfsZoned instance;
    return instance;
}

#ifdef HAVE_LINUX_BLKZONED_H

int HmfsZoned::GetSysFsPath(std::string &devPath, const std::string &attr, std::string &filePath)
{
    struct stat statbuf;
    if (stat(devPath.c_str(), &statbuf) < 0) {
        return -1;
    }

    int32_t majorNum = major(statbuf.st_rdev);
    int32_t minorNum = minor(statbuf.st_rdev);
    std::string sysBlockPath = "/sys/dev/block/" + std::to_string(majorNum) + ":" + std::to_string(minorNum);

    char linkBuf[PATH_MAX];
    ssize_t len = readlink(sysBlockPath.c_str(), linkBuf, sizeof(linkBuf) - 1);
    if (len < 0) {
        return -1;
    }
    linkBuf[len] = '\0';
    std::string resolvedPath(linkBuf);

    std::string sysfsPath = "/sys/dev/block/" + resolvedPath;
    std::string partitionPath = sysfsPath + "/partition";
    if (std::filesystem::exists(partitionPath)) {
        auto lastSlash = sysfsPath.find_last_of('/');
        if (lastSlash == std::string::npos) {
            return -1;
        }
        sysfsPath = sysfsPath.substr(0, lastSlash);
    }

    filePath = sysfsPath + "/" + attr;
    return 0;
}

int HmfsZoned::HmfsGetZonedModel(DeviceInfo *deviceInfo)
{
    HMFS_CHECK(deviceInfo != nullptr, return -1, "Device info is null");
    std::string filePath;
    int res = GetSysFsPath(deviceInfo->path, "queue/zoned", filePath);
    if (res != 0) {
        HMFS_DEBUG("can't find /sys, assuming normal block device.");
        deviceInfo->zonedModel = HMFS_ZONED_NONE;
        return 0;
    }

    std::ifstream file(filePath, std::ifstream::in);
    if (!file) {
        deviceInfo->zonedModel = HMFS_ZONED_NONE;
        return 0;
    }
    std::stringstream buffer;
    buffer << file.rdbuf();
    std::string content = buffer.str();
    content.erase(std::remove(content.begin(), content.end(), '\n'), content.end());
    if (content == "none") {
        deviceInfo->zonedModel = HMFS_ZONED_NONE;
    } else if (content == "host-aware") {
        deviceInfo->zonedModel = HMFS_ZONED_HA;
    } else if (content == "host-managed") {
        deviceInfo->zonedModel = HMFS_ZONED_HM;
    } else {
        HMFS_DEBUG("Unsupported device zoned model");
        return -1;
    }

    return 0;
}

uint32_t HmfsZoned::HmfsGetZoneChunkSectors(DeviceInfo *deviceInfo)
{
    HMFS_CHECK(deviceInfo != nullptr, return 0, "Device info is null");
    std::string filePath;
    int res = GetSysFsPath(deviceInfo->path, "queue/chunk_sectors", filePath);
    if (res != 0) {
        HMFS_DEBUG("\tError: Failed to get device sysfs attribute path\n");
        return 0;
    }

    std::ifstream file(filePath, std::ifstream::in);
    if (!file) {
        return 0;
    }
    std::stringstream buffer;
    buffer << file.rdbuf();
    std::string content = buffer.str();
    content.erase(std::remove(content.begin(), content.end(), '\n'), content.end());
    uint32_t sectors = atoi(content.c_str());
    return sectors;
}

int HmfsZoned::HmfsGetZoneBlocks(DeviceInfo *deviceInfo)
{
    HMFS_CHECK(deviceInfo != nullptr, return -1, "Device info is null");
    deviceInfo->zoneBlocks = 0;
    uint64_t chunkSectors = HmfsGetZoneChunkSectors(deviceInfo);
    if (chunkSectors == 0) {
        return -1;
    }

    deviceInfo->zoneSize = chunkSectors << SECTOR_SIZE_SHIFT;
    deviceInfo->zoneBlocks = chunkSectors >> (HMFS_BLKSIZE_BITS - SECTOR_SIZE_SHIFT);
    chunkSectors = deviceInfo->zoneSize / deviceInfo->sectorSize;

    deviceInfo->zoneCount = deviceInfo->sectorCount / chunkSectors;
    if (deviceInfo->sectorCount % chunkSectors) {
        deviceInfo->zoneCount++;
    }

    return 0;
}

int HmfsZoned::HmfsReportZone(int32_t fd, uint64_t sector, BlockZone *blkZone)
{
    HMFS_CHECK(blkZone != nullptr, return -1, "The zone block is null");
    int ret = -1;
    auto report = std::make_unique<OneZoneReport>();
    OneZoneReport *zoneReport = report.get();
    if (!report) {
        HMFS_DEBUG("No memory for report zones\n");
        return -ENOMEM;
    }
    auto result = memset_s(zoneReport, sizeof(OneZoneReport), 0, sizeof(OneZoneReport));
    HMFS_CHECK(result == EOK, return -1, "Failed to memset zoneReport");
    zoneReport->rep.sector = sector;
    zoneReport->rep.zoneCount = 1;

    ret = ioctl(fd, BLKREPORTZONE, zoneReport);
    if (ret != 0) {
        ret = -errno;
        HMFS_DEBUG("ioctl BLKREPORTZONE failed: errno=%d\n", errno);
        return ret;
    }

    *blkZone = zoneReport->zone;
    return ret;
}

void HmfsZoned::HmfsCheckZonesPart2(DeviceInfo *deviceInfo, BlockZoneReport *report,
    BlockZone *blockZone, uint32_t num, int &lastIsConv)
{
    HMFS_CHECK(deviceInfo != nullptr && report != nullptr && blockZone != nullptr,
        return, "The input parameters contain null pointers");
    if (GetBlockZoneCond(blockZone) == BLK_ZONE_COND_READONLY ||
        GetBlockZoneCond(blockZone) == BLK_ZONE_COND_OFFLINE) {
        lastIsConv = 0;
    }

    if (GetBlockZoneConv(blockZone) || GetBlockZoneSeqPref(blockZone)) {
        if (lastIsConv) {
            deviceInfo->randomZonesCount++;
        }
    } else {
        lastIsConv = 0;
    }

    if (GetBlockZoneConv(blockZone)) {
        HMFS_MINOR_DEBUG("Zone %05u: Conventional, cond 0x%x (%s), sector %" PRIu64 ","
            "%" PRIu64 " sectors",
            num, GetBlockZoneCond(blockZone), GetBlkZoneCondStr(blockZone), GetBlockZoneSector(blockZone),
            GetBlockZoneLength(blockZone));
        deviceInfo->zoneCapBlocks[num] = GetBlockZoneLength(blockZone) >> (HMFS_BLKSIZE_BITS - SECTOR_SIZE_SHIFT);
    } else {
        uint64_t blockZoneSize = GetBlockZoneSize(blockZone, report->flags);
        HMFS_MINOR_DEBUG("Zone %05u: type 0x%x (%s), cond 0x%x (%s), need_reset %d, nonSeq %d,"
            " sector %" PRIu64 ", %" PRIu64 " sectors, capacity %" PRIu64 ", wp sector %" PRIu64 "",
            num, GetBlockZoneType(blockZone), GetBlkZoneTypeStr(blockZone), GetBlockZoneCond(blockZone),
            GetBlkZoneCondStr(blockZone), GetBlockZoneNeedReset(blockZone), GetBlockZoneNonSeq(blockZone),
            GetBlockZoneSector(blockZone), GetBlockZoneLength(blockZone),
            blockZoneSize, GetBlockZoneWpSector(blockZone));
        deviceInfo->zoneCapBlocks[num] = blockZoneSize >> (HMFS_BLKSIZE_BITS - SECTOR_SIZE_SHIFT);
    }
}

int HmfsZoned::HmfsCheckZonesPart(DeviceInfo *deviceInfo, BlockZoneReport *report,
    uint64_t &sector, uint64_t &totalSectors, uint32_t &num)
{
    HMFS_CHECK(deviceInfo != nullptr && report != nullptr, return -1, "The input parameters contain null pointers");
    int lastIsConv = 1;
    while (sector < totalSectors) {
        auto ret = memset_s(report, HMFS_REPORT_ZONES_BUFSZ, 0, HMFS_REPORT_ZONES_BUFSZ);
        HMFS_CHECK(ret == EOK, return -1, "Failed to memset report");
        report->sector = sector;
        report->zoneCount = (HMFS_REPORT_ZONES_BUFSZ - sizeof(BlockZoneReport)) / sizeof(BlockZone);

        if (ioctl(deviceInfo->fd, BLKREPORTZONE, report) != 0) {
            return -errno;
        }

        if (report->zoneCount == 0) {
            break;
        }

        BlockZone *blockZone = reinterpret_cast<BlockZone *>(report + 1);
        for (uint32_t i = 0; i < report->zoneCount && sector < totalSectors; i++) {
            HmfsCheckZonesPart2(deviceInfo, report, blockZone, num, lastIsConv);
            sector = GetBlockZoneSector(blockZone) + GetBlockZoneLength(blockZone);
            num++;
            blockZone++;
        }
    }
    return 0;
}

int HmfsZoned::HmfsCheckZones(DeviceInfo *deviceInfo)
{
    HMFS_CHECK(deviceInfo != nullptr, return -1, "Device info is null");
    uint64_t sector = 0;
    uint32_t num = 0;
    int ret = -1;

    BlockZoneReport *zoneReport = static_cast<BlockZoneReport *>(malloc(HMFS_REPORT_ZONES_BUFSZ));
    if (zoneReport == nullptr) {
        HMFS_DEBUG("No memory for report zones\n");
        return -ENOMEM;
    }

    deviceInfo->zoneCapBlocks = std::make_unique<size_t[]>(deviceInfo->zoneCount);
    (void)memset_s(deviceInfo->zoneCapBlocks.get(), (deviceInfo->zoneCount * sizeof(size_t)), 0,
        (deviceInfo->zoneCount * sizeof(size_t)));
    deviceInfo->randomZonesCount = 0;

    uint64_t totalSectors = (deviceInfo->sectorCount * deviceInfo->sectorSize) >> SECTOR_SIZE_SHIFT;
    ret = HmfsCheckZonesPart(deviceInfo, zoneReport, sector, totalSectors, num);
    if (ret < 0) {
        HMFS_DEBUG("ioctl BLKREPORTZONE failed\n");
        free(zoneReport);
        return ret;
    }

    if (sector != totalSectors) {
        HMFS_DEBUG("nvalid zones: last sector reported is %llu, expected %llu\n",
            static_cast<unsigned long long>((sector << SECTOR_SIZE_SHIFT) / deviceInfo->sectorSize),
            static_cast<unsigned long long>(deviceInfo->sectorCount));
        free(zoneReport);
        return -1;
    }

    if (num != deviceInfo->zoneCount) {
        HMFS_DEBUG("Inconsistent number of zones: expected %u zones, got %u\n", deviceInfo->zoneCount, num);
        free(zoneReport);
        return -1;
    }

    if (deviceInfo->isMetaDevice && deviceInfo->zonedModel == HMFS_ZONED_HM && deviceInfo->randomZonesCount == 0) {
        HMFS_DEBUG("No conventional zone for super block\n");
        free(zoneReport);
        ret = -1;
    }
    free(zoneReport);
    return ret;
}

int HmfsZoned::HmfsResetZonesPart(DeviceInfo *deviceInfo, BlockZoneReport *zoneReport,
    uint64_t &sector, uint64_t sectorCount)
{
    HMFS_CHECK(deviceInfo != nullptr && zoneReport != nullptr, return -1, "The input parameters contain null pointers");
    int ret = 0;
    BlockZone *blockZone = reinterpret_cast<BlockZone *>(zoneReport + 1);
    for (uint32_t i = 0; i < zoneReport->zoneCount && sector < sectorCount; i++) {
        if (GetBlockZoneSeq(blockZone) && !GetBlockZoneEmpty(blockZone)) {
            struct blk_zone_range range;
            range.sector = GetBlockZoneSector(blockZone);
            range.nr_sectors = GetBlockZoneLength(blockZone);
            ret = ioctl(deviceInfo->fd, BLKRESETZONE, &range);
            if (ret != 0) {
                HMFS_DEBUG("Failed to perform ioctl BLKREPORTZONES");
                return -1;
            }
        }
        sector = GetBlockZoneSector(blockZone) + GetBlockZoneLength(blockZone);
        blockZone++;
    }
    return 0;
}

int HmfsZoned::HmfsResetZones(DeviceInfo *deviceInfo)
{
    HMFS_CHECK(deviceInfo != nullptr, return -1, "Device info is null");
    BlockZoneReport *zoneReport = static_cast<BlockZoneReport *>(malloc(HMFS_REPORT_ZONES_BUFSZ));
    if (zoneReport == nullptr) {
        HMFS_DEBUG("Not enough memory for reset zones.");
        return -1;
    }

    uint64_t sector = 0;
    uint64_t sectorCount = (deviceInfo->sectorCount * deviceInfo->sectorSize) / DEFAULT_SECTOR_SIZE;
    while (sector < sectorCount) {
        (void)memset_s(zoneReport, HMFS_REPORT_ZONES_BUFSZ, 0, HMFS_REPORT_ZONES_BUFSZ);
        zoneReport->sector = sector;
        zoneReport->zoneCount = (HMFS_REPORT_ZONES_BUFSZ - sizeof(BlockZoneReport)) / sizeof(BlockZone);
        int32_t ret = ioctl(deviceInfo->fd, BLKREPORTZONE, zoneReport);
        if (ret != 0) {
            HMFS_DEBUG("ioctl BLKREPORTZONES failed");
            free(zoneReport);
            return -1;
        }

        if (zoneReport->zoneCount == 0) {
            break;
        }

        if (HmfsZoned::HmfsResetZonesPart(deviceInfo, zoneReport, sector, sectorCount) < 0) {
            free(zoneReport);
            return -1;
        }
    }
    free(zoneReport);
    HMFS_INFO("Discarded %" PRIu64 " MB", (sector * DEFAULT_SECTOR_SIZE) / (1024 * 1024));
    return 0;
}

uint32_t HmfsZoned::HmfsGetUsableSegments(SuperBlockData *superBlock)
{
    if (superBlock == nullptr) {
        return 0;
    }
    return GetLeValue(superBlock->segmentCountInMain);
}

const char *HmfsZoned::GetBlkZoneTypeStr(BlockZone *blockZone)
{
    HMFS_CHECK(blockZone != nullptr, return "Unknown-type", "The zone block is null");
    switch (GetBlockZoneType(blockZone)) {
        case BLK_ZONE_TYPE_CONVENTIONAL:
            return "Conventional";
        case BLK_ZONE_TYPE_SEQWRITE_REQ:
            return "Sequential-write-required";
        case BLK_ZONE_TYPE_SEQWRITE_PREF:
            return "Sequential-write-preferred";
        default:
            return "Unknown-type";
    }
}

const char *HmfsZoned::GetBlkZoneCondStr(BlockZone *blockZone)
{
    HMFS_CHECK(blockZone != nullptr, return "Unknown-cond", "The zone block is null");
    switch (GetBlockZoneCond(blockZone)) {
        case BLK_ZONE_COND_NOT_WP:
            return "Not-write-pointer";
        case BLK_ZONE_COND_EMPTY:
            return "Empty";
        case BLK_ZONE_COND_IMP_OPEN:
            return "Implicit-open";
        case BLK_ZONE_COND_EXP_OPEN:
            return "Explicit-open";
        case BLK_ZONE_COND_CLOSED:
            return "Closed";
        case BLK_ZONE_COND_READONLY:
            return "Read-only";
        case BLK_ZONE_COND_FULL:
            return "Full";
        case BLK_ZONE_COND_OFFLINE:
            return "Offline";
        default:
            return "Unknown-cond";
    }
}

#else

int HmfsZoned::HmfsReportZone(int32_t fd, uint64_t sector, BlockZone *blkZone)
{
    HMFS_ERROR("%d: Unsupported zoned block device\n", i);
    return -1;
}

int HmfsZoned::HmfsGetZonedModel(DeviceInfo *deviceInfo)
{
    HMFS_CHECK(deviceInfo != nullptr, return -1, "Device info is nullptr");
    deviceInfo->zonedModel = HMFS_ZONED_NONE;
    return 0;
}

int HmfsZoned::HmfsGetZoneBlocks(DeviceInfo *deviceInfo)
{
    HMFS_CHECK(deviceInfo != nullptr, return -1, "Device info is nullptr");
    deviceInfo->zoneCount = 0;
    deviceInfo->zoneBlocks = 0;
    deviceInfo->zonedModel = HMFS_ZONED_NONE;

    return 0;
}

int HmfsZoned::HmfsCheckZones(DeviceInfo *deviceInfo)
{
    HMFS_ERROR("Unsupported zoned block device\n");
    return -1;
}

int HmfsZoned::HmfsResetZones(DeviceInfo *deviceInfo)
{
    HMFS_ERROR("%d: Unsupported zoned block device\n", i);
    return -1;
}

uint32_t HmfsZoned::HmfsGetUsableSegments(SuperBlockData *superBlock)
{
    HMFS_CHECK(superBlock != nullptr, return 0, "Device info is nullptr");
    return GetLeValue(superBlock->segmentCountInMain);
}
#endif
} // namespace Hmfs
} // namespace OHOS
